<template>
	<view class="itu-moment">
		<view :class="{ ituMomentMask: value }" @click="close" @touchmove.stop.prevent="returnHandle"></view>
		<view class="itu-moment__wrap" :style="[{'--itu-moment-border-radius': `${$itu.rpx2px(borderRadius)}px`}]" :class="{ ituMomentShow: value, 'itu-safe-area-inset-bottom': safeAreaInsetBottom }">
			<view class="itu-moment__header" :class="{'itu-moment__header-left': titleAlign==='left'}">
				<text v-if="!$slots['header']">{{title}}</text>
				<slot v-else name="header"></slot>
				<text class="itriton itu-close itu-moment__header-close" @click="close" v-if="closeable"></text>
			</view>
			<view class="itu-moment__action">
				<view class="itu-moment__action__button" :class="{'itu-moment__action__button--disabled': lastDateDisabled()}" @click="lastDate()">
					{{lastDateText}}
				</view>
				<view class="itu-moment__action__text" @click="showCalendar=true">
					<view class="itu-moment__action__text-tip">{{week}}</view>
					<view class="itu-moment__action__text-date">{{today}}</view>
					<text class="itu-moment__action__text-icon itriton itu-arrow-down-fill"></text>
				</view>
				<view class="itu-moment__action__button" :class="{'itu-moment__action__button--disabled': nextDateDisabled()}" @click="nextDate">
					{{nextDateText}}
				</view>
			</view>
			<view class="itu-moment__content">
				<view class="itu-moment__content-item" :class="{
						'itu-moment__disabled': item.disabled,
						'itu-moment__content--start': (mode == 'range' && item.datetime==startMoment) || mode== 'moment',
						'itu-moment__content--end':(mode== 'range' && item.datetime==endMoment) || mode == 'moment',
						'itu-moment__content-item__range': showRangeTime
					}" :style="{backgroundColor: getColor(index)}" @tap.stop="momentClick(index)" v-for="(item, index) in times" :key="item.datetime">
					<view class="itu-moment__content-item__inner" :style="{color: getColor(index,2)}">
						<view class="itu-moment__content-item__text">{{item.range[0]}}</view>
						<view class="itu-moment__content-item__connector" v-if="showRangeTime">｜</view>
						<view class="itu-moment__content-item__text" v-if="showRangeTime">{{item.range[1]}}</view>
					</view>
					<view class="itu-moment__content-item__tips" :style="{color:activeColor}" v-if="mode== 'range' && item.datetime==startMoment && startMoment!=endMoment">{{startText}}</view>
					<view class="itu-moment__content-item__tips" :style="{color:activeColor}" v-if="mode== 'range' && item.datetime==endMoment">{{endText}}</view>
					<view class="itu-moment__content-item__tips" :style="{color: getColor(index,2)}" v-if="item.text && item.datetime!=startMoment && item.datetime!=endMoment">{{item.text}}</view>
				</view>
				<view class="itu-moment__content-block" v-for="item in blockList" :key="item"></view>
			</view>
			<view class="itu-moment__footer">
				<view class="itu-moment__footer-view itu-moment__footer-confirm" :class="[`itu-moment__footer-view--${shape}`]" :style="[{ backgroundColor: activeBgColor }]" @click="submit">{{confirmText}}</view>
			</view>
		</view>
		<itu-calendar v-model="showCalendar" :title="calendarTitle" :weekTextList="calendarWeekTextList" :weekDayList="calendarWeekDayList" :titleAlign="titleAlign" :closeable="closeable" :minDate="innerMinDate" :maxDate="maxDate" :safeAreaInsetBottom="safeAreaInsetBottom" :borderRadius="borderRadius" :shape="shape" :opacity="0" :date="today"
			@change="changeCalendar"></itu-calendar>
	</view>
</template>

<script>
	export default {
		name: "itu-moment",
		props: {
			// 标题
			title: {
				type: String,
				default: "选择时刻"
			},
			// 标题位置
			titleAlign: {
				type: String,
				default: "center"
			},
			// 日期选择标题
			calendarTitle: {
				type: String,
				default: "选择日期"
			},
			// 星期单字列表
			calendarWeekDayList:{
				type: Array,
				default: () => {
					return ['日', '一', '二', '三', '四', '五', '六']
				}
			},
			// 星期文本列表
			calendarWeekTextList: {
				type: Array,
				default: () => {
					return ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
				}
			},
			// 显示时间区间
			showRangeTime:{
				type: Boolean,
				default: true
			},
			// 前一天文本
			lastDateText: {
				type: String,
				default: "前一天"
			},
			// 后一天文本
			nextDateText: {
				type: String,
				default: "后一天"
			},
			// 日期
			date: {
				type: [Number, String],
				default: ''
			},
			// 时刻 mode=moment时生效
			moment: {
				type: String,
				default: ''
			},
			// 时刻 mode=range, continuous=false时生效
			moments: {
				type: Array,
				default: () => {
					return []
				}
			},
			// 时刻 mode=range, continuous=true时生效
			momentRange: {
				type: Array,
				default: () => {
					return []
				}
			},
			// 开始时间
			startTime: {
				type: [Number, String],
				default: 0
			},
			// 结束时间
			endTime: {
				type: [Number, String],
				default: 24
			},
			// 间隔时间（分钟）15/30/45/60
			timeSlot: {
				type: Number,
				default: 60
			},
			// moment-单个时刻选择，range-开始时刻+结束时刻选择
			mode: {
				type: String,
				default: 'moment'
			},
			// 按钮形状 可选值 square、round、circle
			shape: {
				type: String,
				default: "square",
			},
			// 是否开启底部安全区适配
			safeAreaInsetBottom: {
				type: Boolean,
				default: true
			},
			// 是否允许通过点击遮罩
			isMaskClose: {
				type: Boolean,
				default: true
			},
			// 通过双向绑定控制组件的弹出与收起
			value: {
				type: Boolean,
				default: false
			},
			// 最小可选日期,默认当前日期(不在范围内日期禁用不可选)
			minDate: {
				type: [Number, String],
			},
			/**
			 * 最大可选日期
			 * 默认最大值为2200-12-31，之后的日期不可选
			 * 2200-12-31
			 * */
			maxDate: {
				type: [Number, String],
				default: '2200-12-31'
			},
			// 弹窗顶部左右两边的圆角值，单位rpx	
			borderRadius: {
				type: [String, Number],
				default: 0
			},
			// 确认按钮
			confirmText: {
				type: String,
				default: "确认"
			},
			// 选中|起始结束日期背景色
			activeBgColor: {
				type: String,
				default: '#2979ff'
			},
			// 选中|起始结束日期字体颜色
			activeColor: {
				type: String,
				default: '#ffffff'
			},
			// 范围内日期背景色
			rangeBgColor: {
				type: String,
				default: 'rgba(41,121,255,0.13)'
			},
			// 范围内日期字体颜色
			rangeColor: {
				type: String,
				default: '#2979ff'
			},
			// mode=range时生效，起始日期自定义文案
			startText: {
				type: String,
				default: '开始'
			},
			// mode=range时生效，结束日期自定义文案
			endText: {
				type: String,
				default: '结束'
			},
			// 是否显示右上角的关闭图标	
			closeable: {
				type: Boolean,
				default: true
			},
			// mode=range时生效，跨天选择
			daySpan: {
				type: Boolean,
				default: false
			},
			// 星期数据字典
			weekText: {
				type: Array,
				default: () => {
					return ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
				}
			},
			// mode=range时生效，是否连续时段
			continuous: {
				type: Boolean,
				default: true
			},
			// 显示/隐藏过期时间
			showOverdue: {
				type: Boolean,
				default: true
			},
			// 禁用过期
			disabledOverdue:{
				type: Boolean,
				default: true
			},
			// 默认某些时刻状态数组	 [{datetime: '2024-05-29 11:00', disabled: true, text: '已预约'}] 
			momentSlotList: {
				type: Array,
				default: () => {
					return []
				}
			},
			// 过期文本
			expireText: {
				type: String,
				default: '已过期'
			}
		},
		data() {
			return {
				showCalendar: false,
				isStart: true,
				blockList: ['itu_moment_block_1', 'itu_moment_block_2', 'itu_moment_block_3', 'itu_moment_block_4'],
				innerTimeSlot: 0,
				times: [],
				activeMoment: {},
				activeDateTime: "",
				activeMomentList: [],
				today: "",
				week: "",
				innerStartTime: "",
				innerEndTime: "",
				innerMinDate: "",
				innerMinDateTime: "",
				startMoment: "",
				endMoment: "",
			};
		},
		watch: {
			value() {
				if (this.value) this.init();
			},
			today() {
				this.$emit("changeDate", this.today);
			},
			momentSlotList: {
				handler(list) {
					if (list.length) this.times = this.generateTimeSlots();
				}
			},
		},
		mounted() {
			this.init();
		},
		methods: {
			init() {
				let now = this.minDate ? new Date(this.minDate.replace(/\-/g, '/')) : new Date();
				this.today = `${now.getFullYear()}-${this.formatNum(now.getMonth() + 1)}-${this.formatNum(now.getDate())}`;
				this.innerMinDate = this.today;
				this.innerMinDateTime = this.minDate ? new Date(`${this.minDate.replace(/\-/g, '/')} 00:00:00`).getTime() : new Date().getTime();
				this.innerTimeSlot = this.timeSlot / 60
				this.innerStartTime = this.hourToNumber(this.startTime)
				this.innerEndTime = this.hourToNumber(this.endTime)
				this.isStart = true;
				if (this.date) {
					const activeDateTime = new Date(this.date.replace(/\-/g, '/'));
					this.today = `${activeDateTime.getFullYear()}-${this.formatNum(activeDateTime.getMonth() + 1)}-${this.formatNum(activeDateTime.getDate())}`;
				}
				if (this.date && this.moment) {
					this.activeDateTime = `${this.today} ${this.moment}`;
				}
				if (this.momentRange.length && this.continuous) {
					const startMoment = this.momentRange[0];
					this.startMoment = startMoment;
					const endMoment = this.momentRange[1];
					this.endMoment = this.appendEndMoment(endMoment, -1);
					const startMomentDate = new Date(this.startMoment.replace(/\-/g, '/'));
					this.today = `${startMomentDate.getFullYear()}-${this.formatNum(startMomentDate.getMonth() + 1)}-${this.formatNum(startMomentDate.getDate())}`;
				}
				if (this.moments.length && !this.continuous) {
					this.activeMomentList = this.moments
					const startMomentDate = new Date(this.moments[0].replace(/\-/g, '/'));
					this.today = `${startMomentDate.getFullYear()}-${this.formatNum(startMomentDate.getMonth() + 1)}-${this.formatNum(startMomentDate.getDate())}`;
				}
				this.week = this.getWeekText(this.today);
				this.times = this.generateTimeSlots();
			},
			returnHandle() {},
			changeCalendar(e) {
				if (e.result !== this.today) {
					this.today = e.result;
					this.week = this.getWeekText(this.today);
					this.times = this.generateTimeSlots();
					this.resetDateRange();
				}
			},
			close() {
				if (this.isMaskClose) this.$emit('input', false);
			},
			lastDateDisabled() {
				const today = new Date(this.today.replace(/\-/g, '/')).getTime();
				const now = new Date(this.innerMinDateTime).getTime();
				if (today <= now) return true
			},
			lastDate() {
				if (this.lastDateDisabled()) return
				this.resetDateRange();
				const lastDate = new Date(this.today.replace(/\-/g, '/'));
				lastDate.setDate(lastDate.getDate() - 1);
				this.today = `${lastDate.getFullYear()}-${this.formatNum(lastDate.getMonth() + 1)}-${this.formatNum(lastDate.getDate())}`;
				this.week = this.getWeekText(this.today);
				this.times = this.generateTimeSlots();
			},
			nextDate() {
				if (this.nextDateDisabled()) return
				this.resetDateRange();
				const nextDay = new Date(this.today.replace(/\-/g, '/'));
				nextDay.setDate(nextDay.getDate() + 1);
				this.today = `${nextDay.getFullYear()}-${this.formatNum(nextDay.getMonth() + 1)}-${this.formatNum(nextDay.getDate())}`;
				this.week = this.getWeekText(this.today);
				this.times = this.generateTimeSlots();
			},
			nextDateDisabled() {
				const today = new Date(this.today.replace(/\-/g, '/')).getTime();
				const maxDate = new Date(this.maxDate.replace(/\-/g, '/')).getTime();
				if (today >= maxDate) return true
			},
			resetDateRange() {
				// 不允许跨天选择并且已经选择一项时间
				if ((!this.daySpan && !this.isStart)) {
					this.isStart = true;
					this.startMoment = "";
					this.endMoment = "";
				}
			},
			formatNum(num) {
				return num < 10 ? '0' + num : num + '';
			},
			getWeekText(date) {
				date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
				let week = date.getDay();
				return this.weekText[week];
			},
			getColor(index, type = 1) {
				let color = type == 1 ? '' : this.color;
				const datetime = this.times[index].datetime;
				const timestamp = new Date(datetime.replace(/\-/g, '/')).getTime();
				const start = this.startMoment.replace(/\-/g, '/')
				const end = this.endMoment.replace(/\-/g, '/')
				if (this.activeDateTime === datetime || this.startMoment == datetime || this.endMoment == datetime || (!this.continuous && this.activeMomentList.includes(datetime))) {
					color = type == 1 ? this.activeBgColor : this.activeColor;
				} else if (this.continuous && this.endMoment && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
					color = type == 1 ? this.rangeBgColor : this.rangeColor;
				}
				return color
			},
			momentClick(index) {
				const datetime = this.times[index].datetime;
				if (this.mode === 'moment') {
					this.activeMoment = this.times[index];
					this.activeDateTime = datetime
				} else {
					if (this.continuous) {
						let compare = new Date(datetime.replace(/\-/g, '/')).getTime() < new Date(this.startMoment.replace(/\-/g, '/')).getTime()
						if (this.isStart || compare) {
							this.startMoment = datetime;
							this.endMoment = "";
							this.isStart = false;
						} else {
							const inMomentRange = this.isInMomentRange(this.startMoment, datetime);
							if (inMomentRange) {
								this.startMoment = datetime;
								this.endMoment = "";
								this.isStart = false;
							} else {
								this.endMoment = datetime;
								this.isStart = true;
							}
						}
					} else {
						// 不跨天，选择日期的时候清空已选数据
						const date = this.times[index].date;
						const activeMoment = this.activeMomentList.length ? new Date(this.activeMomentList[0].replace(/\-/g, '/')) : new Date()
						const activeDate = `${activeMoment.getFullYear()}-${this.formatNum(activeMoment.getMonth() + 1)}-${this.formatNum(activeMoment.getDate())}`;
						if (!this.daySpan && this.today != activeDate) this.activeMomentList = [];
						const existIndex = this.activeMomentList.indexOf(datetime);
						if (existIndex > -1) this.activeMomentList.splice(existIndex, 1);
						else this.activeMomentList.push(datetime);
						this.activeMomentList.sort((a, b) => new Date(a.replace(/\-/g, '/')) - new Date(b.replace(/\-/g, '/')));
					}
				}
			},
			hourToNumber(time) {
				if (typeof time === 'string') {
					if (!isNaN(time)) {
						return Number(time)
					} else {
						const timeString = time;
						// 使用冒号分割时间字符串
						const timeParts = timeString?.split(":");
						// 如果分割后的数组长度小于等于1，返回0
						if (timeParts.length <= 1) return 0;
						// 转换时间字符串的第一个部分为整数
						let result = parseInt(timeParts[0], 10);
						if (timeParts.length > 1 && parseInt(timeParts[1]) !== 0) {
							result = result + parseInt(timeParts[1]) / 60;
						}
						return result;
					}
				} else return time;
			},
			timeStringToNumber(time) {
				if (typeof time === 'string') {
					if (!isNaN(time)) {
						return Number(time)
					} else {
						const timeString = time;
						// 使用冒号分割时间字符串
						const timeParts = timeString?.split(":");
						// 如果分割后的数组长度小于等于1，返回0
						if (timeParts.length <= 1) return 0;
						// 转换时间字符串的第一个部分为整数
						let result = parseInt(timeParts[0], 10);
						if (timeParts.length > 1 && parseInt(timeParts[1]) !== 0) {
							result = result + parseInt(timeParts[1]) / 60;
						}
						return result;
					}
				} else return time;
			},
			numberToTime(number) {
				const hour = Math.floor(number);
				const minute = Math.round((number - hour) * 60);
				return `${this.formatNum(hour)}:${this.formatNum(minute)}`;
			},
			timeToNumber(timeString) {
				const [hour, minute] = timeString.split(':').map(parseFloat);
				return hour + minute / 60;
			},
			momentSlotInDatetime(datetime) {
				let result = null;
				for (let i = 0; i < this.momentSlotList.length; i++) {
					const momentSlot = this.momentSlotList[i];
					if (momentSlot.datetime === datetime) {
						result = {
							text: momentSlot.text,
							disabled: momentSlot.disabled
						}
						break;
					}
				}
				return result
			},
			isInMomentRange(startMoment, endMoment) {
				let result = false;
				const startTimeMillis = new Date(startMoment.replace(/\-/g, '/')).getTime();
				const endTimeMillis = new Date(endMoment.replace(/\-/g, '/')).getTime();
				for (let i = 0; i < this.momentSlotList.length; i++) {
					const momentSlot = this.momentSlotList[i];
					const currentTime = new Date(momentSlot.datetime.replace(/\-/g, '/')).getTime();
					if (currentTime >= startTimeMillis && currentTime <= endTimeMillis && momentSlot.disabled) {
						result = true
						break
					}
				}
				return result
			},
			generateTimeSlots() {
				let slots = [];
				let currentTime = this.innerStartTime;
				const timestamp = this.innerMinDateTime;
				while (currentTime < this.innerEndTime) {
					const nextTime = currentTime + this.innerTimeSlot;
					const time = this.numberToTime(currentTime)
					const currentDateTime = `${this.today} ${time}`;
					const currentDateTimestamp = new Date(currentDateTime.replace(/\-/g, '/')).getTime();
					let disabled = false;
					let text = "";
					// 要判断数据是否过期
					if (timestamp > currentDateTimestamp && this.disabledOverdue) {
						disabled = true;
						text = this.expireText;
					}
					// 处理momentSlotList数据
					const momentSlot = this.momentSlotInDatetime(currentDateTime);
					if (momentSlot) {
						text = momentSlot.text;
						disabled = momentSlot.disabled;
					}
					if (this.showOverdue || (!this.showOverdue && !disabled)) {
						slots.push({ moment: currentTime, disabled, text, date: this.today, time, datetime: currentDateTime, range: [this.numberToTime(currentTime), this.numberToTime(nextTime)] });
					}
					currentTime = nextTime
				}
				return slots;
			},
			appendEndMoment(datetime, type = 1) {
				const timeList = datetime.split(" ");
				const date = timeList[0];
				const time = timeList[1];
				const timeNumber = this.timeStringToNumber(time) + type * this.innerTimeSlot;
				let moment = this.numberToTime(timeNumber);
				return `${date} ${moment}`
			},
			submit() {
				this.close();
				let result = {};
				if (this.mode === 'moment') result = this.activeMoment;
				else {
					if (this.continuous) {
						if (this.startMoment && this.endMoment) {
							const endMoment = this.appendEndMoment(this.endMoment);
							result = {
								momentRange: [this.startMoment, endMoment],
								startMoment: this.startMoment,
								endMoment: endMoment
							}
						} else {
							this.startMoment = ""
							this.endMoment = ""
							this.isStart = true
						}
					} else {
						let momentRanges = [];
						this.activeMomentList.forEach(item => {
							momentRanges.push({
								datetime: item,
								range: [item, this.appendEndMoment(item)]
							})
						})
						result = {
							moments: this.activeMomentList,
							momentRanges: momentRanges,
						}
					}
				}
				this.$emit("change", result);
			}
		}
	}
</script>

<style lang="scss">
	.itu-moment {
		color: #606266;
		font-size: 28rpx;

		&__wrap {
			position: fixed;
			bottom: 0;
			left: 0;
			width: 100%;
			transition: all 0.3s ease;
			transform: translateY(100%);
			z-index: 998;
		}

		&__header {
			background: #fff;
			display: flex;
			justify-content: center;
			flex: 1;
			height: 96rpx;
			font-size: 30rpx;
			padding: 0 20rpx;
			align-items: center;
			color: #282828;
			border-bottom: 1rpx solid $itu-border-color;

			&-left {
				justify-content: flex-start;
			}

			&-close {
				position: absolute;
				right: 20rpx;
			}
		}

		&__action {
			display: flex;
			justify-content: space-between;
			align-items: center;
			padding: 20rpx;
			border-bottom: 1rpx solid $itu-border-color;
			background: #fff;

			&__button {
				display: flex;
				justify-content: center;
				align-items: center;
				border: 2rpx solid $itu-border-color;
				padding: 0 30rpx;
				border-radius: 8rpx;
				height: 70rpx;

				&--disabled {
					background-color: #F7F7F7;
					color: #CDCDCD;
				}
			}

			&__text {
				display: flex;
				flex-direction: column;
				justify-content: center;
				align-items: center;
				padding: 0 16rpx;
				font-weight: bold;

				&-tip {
					font-size: 20rpx;
					line-height: 22rpx;
					font-weight: normal;
				}

				&-date {
					width: 200rpx;
					text-align: center;
					height: 38rpx;
					line-height: 38rpx;
				}

				&-icon {
					font-size: 20rpx;
					line-height: 20rpx;
					font-weight: normal;
					margin-top: -4rpx;
				}
			}
		}

		&__content {
			display: flex;
			width: 100%;
			flex-wrap: wrap;
			justify-content: space-between;
			align-content: flex-start;
			padding: 20rpx 20rpx 0 20rpx;
			box-sizing: border-box;
			background-color: #fff;
			position: relative;
			height: 546rpx;
			border-bottom: 1rpx solid $itu-border-color;
			overflow-y: auto;

			&-block {
				width: 110rpx;
				height: 1rpx;
				position: relative;
				overflow: hidden;
			}

			&-item {
				display: flex;
				padding: 6px 0;
				overflow: hidden;
				position: relative;
				z-index: 2;
				width: 110rpx;
				height: 110rpx;
				border: 2rpx solid $itu-border-color;
				border-radius: 6rpx;
				margin-bottom: 20rpx;

				&__text {
					font-size: 26rpx;
					line-height: 28rpx;
				}

				&__connector {
					font-size: 20rpx;
					line-height: 20rpx;
					transform: scale(.8);
				}

				&__range {
					height: 140rpx;
				}

				&__inner {
					display: flex;
					align-items: center;
					justify-content: center;
					flex-direction: column;
					font-size: 26rpx;
					position: relative;
					width: 100%;

					&__desc {
						width: 100%;
						font-size: 24rpx;
						line-height: 24rpx;
						transform: scale(0.75);
						transform-origin: center center;
						position: absolute;
						left: 0;
						text-align: center;
						bottom: 2rpx;
					}
				}

				&__tips {
					width: 100%;
					font-size: 24rpx;
					line-height: 24rpx;
					position: absolute;
					left: 0;
					transform: scale(0.8);
					transform-origin: center center;
					text-align: center;
					bottom: 6rpx;
					z-index: 2;
				}
			}

			&--end {
				border-top-right-radius: 8rpx;
				border-bottom-right-radius: 8rpx;
			}

			&--start {
				border-top-left-radius: 8rpx;
				border-bottom-left-radius: 8rpx;
			}
		}

		&__disabled {
			background-color: #F7F7F7;
			color: #CDCDCD;
			pointer-events: none;
		}

		&__footer {
			display: flex;
			align-items: center;
			font-size: 30rpx;
			padding: 20rpx;

			&-view {
				display: flex;
				align-items: center;
				justify-content: center;
				flex: 1;
				text-align: center;
				height: 88rpx;

				&--round {
					border-radius: 12rpx;

					&:last-child {
						margin-left: 0;
					}
				}

				&--circle {
					border-radius: 44rpx;

					&:last-child {
						margin-left: 0;
					}
				}
			}

			&-confirm {
				color: #fff;
			}
		}
	}

	.ituMomentMask {
		position: fixed;
		z-index: 998;
		top: 0;
		right: 0;
		left: 0;
		bottom: 0;
		background: rgba(0, 0, 0, 0.6);
	}

	.ituMomentShow {
		overflow: hidden;
		transform: translateY(0);
		background-color: #FFF;
		border-radius: var(--itu-moment-border-radius) var(--itu-moment-border-radius) 0 0;
	}
</style>